//
//  GIFAnimator.swift
//  GifUsage
//
//  Created by Ting Han on 2020/12/19.
//  Copyright © 2020 Ting Han. All rights reserved.
//

import UIKit

class GIFAnimator {
    var frameStore: GIFFrameStore?
    
    private weak var delegate: GIFAnimatable!
    private var animationBlock: (() -> Void)? = nil
    
    public init(withDelegate delegate: GIFAnimatable) {
        self.delegate = delegate
    }
    
    var isAnimating: Bool {
        return !displayLink.isPaused
    }
    
    func startAnimating() {
        if frameStore?.isGIF ?? false {
            displayLink.isPaused = false
        }
    }
    
    func stopAnimating() {
        displayLink.isPaused = true
    }
    
    fileprivate func updateFrameIfNeeded() {
        guard let store = frameStore else { return }
        if store.isFinished {
            stopAnimating()
            if let animationBlock = animationBlock {
                animationBlock()
            }
            return
        }
        
        store.shouldChangeFrame(with: displayLink.duration) {
            if $0 {
                delegate.animatorHasNewFrame()
            }
        }
    }
    
    private func attachDisplayLink() {
        displayLink.add(to: .main, forMode: RunLoop.Mode.common)
    }
    
    private var displayLinkInitialized: Bool = false
    private lazy var displayLink: CADisplayLink = { [unowned self] in
        self.displayLinkInitialized = true
        let display = CADisplayLink(target: DisplayLinkProxy(target: self), selector: #selector(DisplayLinkProxy.onScreenUpdate))
        display.isPaused = true
        return display
    }()
    
    deinit {
        if displayLinkInitialized {
            displayLink.invalidate()
        }
    }
}

extension GIFAnimator {
    func animate(withGIFNamed imageName: String, size: CGSize, contentMode: UIView.ContentMode, loopCount: Int = 0, preparationBlock: (() -> Void)? = nil, animationBlock: (() -> Void)? = nil) {
        self.animationBlock = animationBlock
        prepareForAnimation(withGIFNamed: imageName,
                            size: size,
                            contentMode: contentMode,
                            loopCount: loopCount,
                            completionHandler: preparationBlock)
        startAnimating()
    }
    
    func animate(withGIFData imageData: Data, size: CGSize, contentMode: UIView.ContentMode, loopCount: Int = 0, preparationBlock: (() -> Void)? = nil, animationBlock: (() -> Void)? = nil)  {
        self.animationBlock = animationBlock
        prepareForAnimation(withGIFData: imageData,
                            size: size,
                            contentMode: contentMode,
                            loopCount: loopCount,
                            completionHandler: preparationBlock)
        startAnimating()
    }
    
    func prepareForAnimation(withGIFNamed imageName: String, inBundle bundle: Bundle = .main, size: CGSize, contentMode: UIView.ContentMode, loopCount: Int = 0, completionHandler: (() -> Void)? = nil) {
        guard let extensionRemoved = imageName.components(separatedBy: ".")[safe: 0],
              let imagePath = bundle.url(forResource: extensionRemoved, withExtension: "gif"),
              let data = try? Data(contentsOf: imagePath) else { return }
        
        prepareForAnimation(withGIFData: data,
                            size: size,
                            contentMode: contentMode,
                            loopCount: loopCount,
                            completionHandler: completionHandler)
    }
    
    func prepareForAnimation(withGIFData imageData: Data, size: CGSize, contentMode: UIView.ContentMode, loopCount: Int = 0, completionHandler: (() -> Void)? = nil) {
        frameStore = GIFFrameStore(data: imageData,
                                   size: size,
                                   contentMode: contentMode,
                                   framePreloadCount: 50,
                                   loopCount: loopCount)
        frameStore!.shouldResizeFrames = true
        frameStore!.prepareFrames(completionHandler)
        attachDisplayLink()
    }
    
    func prepareForReuse() {
        stopAnimating()
        frameStore = nil
    }
}

fileprivate class DisplayLinkProxy {
    private weak var target: GIFAnimator?
    init(target: GIFAnimator) {
        self.target = target
    }
    
    @objc func onScreenUpdate() {
        print("display link")
        target?.updateFrameIfNeeded()
    }
}
